<?

// ****************************************************************************
// ---- NEW ZAPATA PART STARTS HERE -------------------------------------------
// ****************************************************************************

/**
* Get zapata card(s) by id from database
*
* @param integer $id
* @return ZapCardArray
*/

function getZapataCard($id) {

  global $db;

  if ($id != "") $condition = "AND `id` = '$id'";

  $qry="SELECT * FROM `cw_zapata_card` WHERE `active` = TRUE $condition ORDER BY `id`";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $ret[]=$arr;

  return $ret;
}


/**
* Insert zapata card into db (basic parameter) and update span count.
* To complete the card installation, we need more information like
* Port Type, Device, ... (see updateZapataPort)
*
* @param integer $id
* @return integer card_id = ok, 0 = error (insert failed)
*/

function putZapataCard($id) {

  global $db;

  // get basic card paramter, no of ports, driver load order,
  // port type(s) and check if this is a span type card.

  $qry="SELECT * FROM `cw_zapata_card` WHERE `id` = $id";
  $res=$db->Execute($qry);
  $arr=$res->FetchRow();
  $ports=$arr["ports"];
  $order=$arr["order"];
  $type =$arr["type"];
  $span =$arr["span"];

  // check if this card can configure the port type

  $options_type=split(',',$type);
  $options=count($options_type);

  // read the id of the previous installed card 

  $qry="SELECT MAX(`card_id`) as lastid FROM `cw_zapata_port`";
  $res=$db->Execute($qry);
  $arr=$res->FetchRow();
  $card_id=$arr["lastid"];
  if ($card_id != "NULL") $card_id++; else $card_id=1;

  // insert basic paramter for this card

  $res=true;

  for ($i = 1; $i <= $ports; $i++) {
    if ($options == 1) { // Card cannot configure the port type
      $qry="INSERT INTO `cw_zapata_port` (`port`,`span`,`order`,`card`,`card_id`,`type`) VALUES ('$i','$span','$order','$id','$card_id','$type')";
    } else {             // Card can configure port type
      $qry="INSERT INTO `cw_zapata_port` (`port`,`span`,`order`,`card`,`card_id`)        VALUES ('$i','$span','$order','$id','$card_id')";
    }
    $res&=$db->Execute($qry);
  }

  // Next, set correct span numbers in database

  $ports = getZapataPort(0);
  $spans = 1; // Spans start counting with 1

  if ($res == true) { // $res is true after INSERT ? ... see ISDN putCard
                      // Hmmm, $res = 1, but not identical so == not ===
    foreach ($ports as $p) {
      $id   = $p["id"];
      $span = $p["span"];
      if ($span) {
        $qry="UPDATE `cw_zapata_port` SET `span` = '$spans' WHERE `id` = '$id'";
        $db->Execute($qry);
        $spans++;
      }
    }
  }

  // If insert was successfull, return card_id (should we check update too?)

  if ($res == true) $res = $card_id; else $res = false;

  return $res;
}


/**
* Delete zapata card port(s), channel(s) by the card id
*
* @param integer $id (card_id)
* @return integer 1 = ok, 0 = error
*
* @todo perhaps we should refuse to delete a card if the card is inuse
*       say a 4 port E1 card with channel banks = ~ 120 Subscribers
*       so one command can delete most of the system ...
*/

function delZapataCard($id) {

  global $db;

  $res = true;

  $qry="SELECT * FROM `cw_zapata_port` WHERE `card_id` = '$id'";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $ports[]=$arr;

  if(is_array($ports)) { 
    foreach ($ports as $p) {
      $id = $p["id"];

      // fetch channel list for this port
      $channel=getZapataChannel($id);
      
      // delete channels from cw_zapata_portgroup
      if(is_array($channel)) {
        foreach ($channel as $c) {
          $cid=$c["id"];
          $qry="DELETE from `cw_zapata_portgroup` WHERE `port` = '$cid'"; 
          $db->Execute($qry);
        }
      }

      // delete channels of this port in cw_zapata_channel
      delZapataChannel($id);

      // delete port entry
      $qry="DELETE FROM `cw_zapata_port` WHERE `id` = '$id'"; 
      $res=$db->Execute($qry);
    }
  }

  // Next, set correct span numbers in database

  $ports = getZapataPort(0);
  $spans = 1; // Spans start counting at 1

  if(is_array($ports)) { 
    foreach ($ports as $p) {
      $id   = $p["id"];
      $span = $p["span"];
      if ($span) {
        $qry="UPDATE `cw_zapata_port` SET `span` = '$spans' WHERE `id` = '$id'";
        $db->Execute($qry);
        $spans++;
      }
    }
  }

  // Map local channels to zaptel channel numbers

  $channel  = getZapataChannel(0);
  $zchannel = 0;

  if (is_array($channel)) {
    foreach($channel as $c) {
      $id     = $c["id"];
      $device = $c["device"];
      $zchannel++;    
      $qry="UPDATE `cw_zapata_channel` SET `zchannel` = '$zchannel' WHERE `id` = '$id'";
      $res=$db->Execute($qry);
    }
  }

  // delete or update the zapata groups 

  $qry="SELECT `id` FROM `cw_zapata_group`";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $groups[]=$arr;

  if (is_array($groups)) {
    foreach ($groups as $g) {
      $group = getZapataGroup($g["id"]);
      if(!putZapataGroup($group["0"],false))
        delZapataGroup($g["id"],false);
    }
  }
  
  // update zapata.conf and restart asterisk

  if (!writeZaptelInitConfFile(true)) {
    return 0;
  }
  return 1;
}


/**
* Get zapata card port options by type (E1,T1,FXO,FXS)
*
* @param string $type
* @return ZapPortOptionArray
*/

function getZapataPortOption($type) {

  global $db;

  if ($type!="") $condition="WHERE `type` = '$type'";

  $qry="SELECT * FROM `cw_zapata_option` $condition";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $ret[]=$arr;

  return $ret;
}


/**
* Get zapata card port(s) by id (port_id)
*
* @param integer $id
* @return ZapPortArray
*/

function getZapataPort($id) {

  global $db;

  if ($id!="") $condition = "WHERE `id` = '$id'";
  //
  // DO NOT CHANGE THIS QUERY IT WILL RETURN THE CARDS, PORTS IN LOAD ORDER
  //
  $qry="SELECT * FROM `cw_zapata_port` $condition ORDER BY `order`, `card_id`, `port`";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $ret[]=$arr;

  return $ret;
}


/**
* Update zapata card port(s) and tonezone(s)
*
* // the Tone Zone could be a separate function
* @param ZapPortArray $ports, ZapToneZoneArray $zone
* @return integer 1 = ok, 0 = error
*/

function updateZapataPort($ports,$zones) {

  global $db;

  $res = true;
  $ret = true;

  $deleteZapataChannel=false; // delete channel for port_id
  $updateZapataChannel=false; // update channel for port_id
  $createZapataChannel=false; // create channel for port_id
  $updateZapataGroups =false; // update zapata groups
  $updateZapataPort   =false; // update port paramter
  $writeZaptelInitFile=false; // write the new zaptel.conf, zaptel files


// Note: perhaps the Tone Zone should not be here in this function
  $qry="UPDATE cw_zapata_zone SET `defaultzone`='0',`loadzone`='0'"; 
  $res=$db->Execute($qry);

  if (is_array($zones)) {
    foreach ($zones as $z) {
      $loadzone=$z["loadzone"];
      $defzone =$z["defzone"];
      $qry="UPDATE `cw_zapata_zone` SET `loadzone`='1' WHERE name='$loadzone'"; 
      $res=$db->Execute($qry);
//      $new_loadzone.=$loadzone.",";
    }
  }
  if ($defzone) {
    $qry="UPDATE `cw_zapata_zone` SET `defaultzone`='1', `loadzone`='1' WHERE name='$defzone'"; 
    $res=$db->Execute($qry);
  }
// End of the stupid Zone update ... move into own function


  // We need to have at least a db entry for each port and also 
  // a valid type per span port in the update request else we
  // refuse this update request as we cannot determine the channel
  // numbers correctly without E1/T1 specified.

  foreach ($ports as $p) {

    $port = getZapataPort($p["id"]);

    // This should never happen but can't hurt to check if we have a port

    if ($port[0]["id"] != $p["id"]) {
      error_log("Abort - updateZapataPort(): cannot find card port in db");
      return 0;
    }

    // In order to compute channels properly we must have type E1, T1

    if ($port[0]["span"] && ($p["type"] != "E1" && $p["type"] != "T1")) { 
      error_log("Abort - updateZapataPort(): no type specified for span");
      return 0;
    }
  }

  // Well we should have everything we need now to
  // create a configuration that can pass ztcfg

  foreach ($ports as $p) {
    $p_id      = $p["id"];      // port_id from web update request
    $p_type    = $p["type"];    // E1,T1       - FXO,FXS
    $p_device  = $p["device"];  // PRI,E&M,FX? - fx?ks,fx?ls,fx?gs
    $p_timing  = $p["timing"];  // 0,1,2,3
    $p_lbo     = $p["lbo"];     // 0,1,2,3,4,5,6,7
    $p_framing = $p["framing"]; // ccs,cas,esf,d4
    $p_coding  = $p["coding"];  // hdb3,ami,b8zs
    $p_crc4    = $p["crc4"];    // 0,1
    $p_yellow  = $p["yellow"];  // 0,1


    $c=getZapataPort($p_id);    // current port configuration 

    $c_id      = $c[0]["id"];	// Port Id
    $c_port    = $c[0]["port"]; // Port Number
    $c_type    = $c[0]["type"];
    $c_device  = $c[0]["device"]; 
    $c_span    = $c[0]["span"];
    $c_timing  = $c[0]["timing"];
    $c_lbo     = $c[0]["lbo"];
    $c_framing = $c[0]["framing"];
    $c_coding  = $c[0]["coding"];
    $c_crc4    = $c[0]["crc4"];
    $c_yellow  = $c[0]["yellow"];


    // Action after all the paramter checks ....

    $deleteZapataChannel=false; // delete channel for port_id
    $updateZapataChannel=false; // update channel for port_id
    $createZapataChannel=false; // create channel for port_id
    $updateZapataPort   =false; // update port paramter


    // ***************************************************************
    // *** FXO,FXS - CARDS
    // ***************************************************************

    if(!$c_span) { // This is a FXO, FXS card - sanitize paramters 

      if($p_type != "FXO" && $p_type != "FXS") {
        $p_type   = "FX?";
        $p_device = "unused"; 
      }

      // Try to be smart about device names (must end in ks,ls,gs)

      if (strlen($p_device)) {
        if($p_type == "FXO") { $tmp="fxs"; $tmp.=substr($p_device,-2,2); }
        if($p_type == "FXS") { $tmp="fxo"; $tmp.=substr($p_device,-2,2); }
        if($p_type == "FXO" || $p_type == "FXS") $p_device = $tmp;
      }

      // Do we have a valid device for this port?

      if (($p_device  != "fxsks") && 
          ($p_device  != "fxsls") && 
          ($p_device  != "fxsgs") &&
          ($p_device  != "fxoks") && 
          ($p_device  != "fxols") && 
          ($p_device  != "fxogs") &&
          ($p_device  != "unused")) {
        $p_device = "unused"; // This should always work
      }

      // FXO,FXS cards don't use span type parameter

      $p_timing  = NULL;
      $p_lbo     = NULL;
      $p_framing = NULL;
      $p_coding  = NULL;
      $p_crc4    = NULL;
      $p_yellow  = NULL;


      // Check if port has already channels

      $channel=getZapataChannel($p_id);

      if (!is_array($channel)) { // port has no channels

         $createZapataChannel = true;
         $updateZapataPort    = true;

      } else {                   // port has channels

        if ($p_type != $c_type && !$channel[0]["available"]) {
          $ret = false;
          continue; // Do not change device type if port inuse
        }

        if ($p_type != $c_type &&  $channel[0]["available"]) {
          $channel[0]["type"]  = $p_type;
          $updateZapataChannel = true;
          $updateZapataPort    = true;
        }    

        if ($p_device != $c_device && !$channel[0]["available"]) {
          $ret = false;
          continue; // Do not change device if port inuse
        }

        if ($p_device != $c_device &&  $channel[0]["available"]) {

          // The FXO,FXS cards I know, can be ks,ls or gs 
          // Find matching signalling for this device

          switch($p_device) {
            case "fxoks": $channel[0]["signalling"]="fxo_ks"; break;
            case "fxols": $channel[0]["signalling"]="fxo_ls"; break;
            case "fxogs": $channel[0]["signalling"]="fxo_gs"; break;
            case "fxsks": $channel[0]["signalling"]="fxs_ks"; break;
            case "fxsls": $channel[0]["signalling"]="fxs_ls"; break;
            case "fxsgs": $channel[0]["signalling"]="fxs_gs"; break;
            default:      $channel[0]["signalling"]="";       break;
          }
          $channel[0]["device"]= $p_device;
          $updateZapataChannel = true;
          $updateZapataPort    = true;
        }
      } // End FXO,FXS Cards

    // ***************************************************************
    // *** SPAN - CARDS
    // ***************************************************************

    } else { // This is a span card - sanitize parameters

      // We already checked that we have a valid $p_type T1,E1
      // before going into the foreach ports as p loop
      // All depends on having a valid $p_type from here on

      // Do we have a valid device for this port?

      if (($p_device  != "PRI") && 
          ($p_device  != "E&M") &&
          ($p_device  != "FX?")) {
        $p_device = NULL; // This should catch it all ...
      }

      // Every valid device requires a valid span config

      if (!ctype_digit($p_timing) && !ctype_digit($p_lbo) ||
         ($p_framing != "ccs" && $p_framing != "cas" && 
          $p_framing != "esf" && $p_framing != "d4") ||
         ($p_coding != "hdb3" && $p_coding  != "ami" && $p_coding != "b8zs")){
        $p_device = NULL; // requested spanconfig cannot pass ztcfg test

      } else {

        // Requested span config is valid, any changes to previous one

        if ($p_timing  != $c_timing  ||
            $p_lbo     != $c_lbo     ||
            $p_framing != $c_framing ||
            $p_coding  != $c_coding  ||
            $p_crc4    != $c_crc4    ||
            $p_yellow  != $c_yellow) {
          $updateZapataPort = true;
        } 
      }

      // Now we either have:
      // ------------------------------------------------------------
      // valid span config and valid device -> create valid device(s)
      // valid span config and no device    -> create device="unused"
      //    - or - both invalid             -> create device="unused"
      // ------------------------------------------------------------


      // Check if port has already channels

      $channel=getZapataChannel($p_id);

      if (!is_array($channel)) { // no channels found

        $createZapataChannel = true;
        $updateZapataPort    = true;

      } else {                   // ok we have channels already

        // Check is this span is inuse or now

        $spaninuse = 0;

        foreach($channel as $c) {
          if ($c["available"] != 1)
            $spaninuse++;
        }

        if ($p_type != $c_type && $spaninuse != 0) {
          $ret = false;
          continue; // Do not change port type if span is inuse
        }

        if ($p_type != $c_type && $spaninuse == 0) {
          $deleteZapataChannel = true;
          $createZapataChannel = true;
          $updateZapataPort    = true;
        }    

        if ($p_device != $c_device && $spaninuse != 0) {
          $ret = false;
          continue; // Do not change device type if span is inuse
        }

        if ($p_device != $c_device && $spaninuse == 0) {
          $deleteZapataChannel = true;
          $createZapataChannel = true;
          $updateZapataPort    = true;
        }
      } // End SPAN Cards
    } 

    // ***************************************************************
    // *** Now do the updates
    // ***************************************************************

    if ($updateZapataPort == true) { // First update the port paramter
      $qry="UPDATE `cw_zapata_port` SET `type` = '$p_type', `device` ='$p_device', `timing` = '$p_timing',`lbo`='$p_lbo', `framing`='$p_framing', `coding`='$p_coding',`crc4`='$p_crc4',`yellow`='$p_yellow' WHERE `id` = '$p_id'"; 
      $res=$db->Execute($qry);

      if($db->Affected_Rows()) {
        $writeZaptelInitFile = true;   // Update zaptel.conf, zaptel too
      }
    }

    if ($updateZapataChannel == true) {
      if(updateZapataChannel($channel)) {
        $writeZaptelInitFile = true;
      }
    }

    if ($deleteZapataChannel == true) {
      if(delZapataChannel($p_id)) {
        $updateZapataGroups  = true;
        $writeZaptelInitFile = true;
      }
    }

    if ($createZapataChannel == true) {
      if(putZapataChannel($p_id)) {
        $updateZapataGroups  = true;
        $writeZaptelInitFile = true;
      }
    }

  } // End (foreach ports as p)


  // Do we need to update zapata groups?

  // Note: currently we refuse to change anything important
  //       on ports or channels inuse, so I guess we do not
  //       have to expect channels disappearing that are used 

  if ($updateZapataGroups == true) {

    // update the zapata groups to update zap channels ?

    $qry="SELECT `id` FROM `cw_zapata_group`";
    $res=$db->Execute($qry);

    while ($arr=$res->FetchRow()) 
      $groups[]=$arr;

    if (is_array($groups)) {
      foreach ($groups as $g) {
        $group = getZapataGroup($g["id"]);
        putZapataGroup($group["0"],false);
      }
    }
  }

  // Do we need to write new config file(s) ?

  if ($writeZaptelInitFile == true) {
    if(!writeZaptelInitConfFile(true)) {
      $ret = false;
    }
  }

  return $ret;
}


/**
* Get zapata channel(s) channel order
*
* @param integer $id
* @return ZapChannelArray
*/

function getZapataChannel($id) {

  global $db;

  if ($id != "") $condition="WHERE `port_id` = '$id'";
  //
  // DO NOT CHANGE THIS QUERY IT WILL RETURN THE CHANNELS IN ZAPTEL ORDER
  //
  $qry="SELECT * FROM `cw_zapata_channel` $condition ORDER BY `order`, `port_id`, `pchannel`";
  $res=$db->Execute($qry);
  
  while ($arr=$res->FetchRow()) 
    $ret[]=$arr;
  
  return $ret;
}


/**
* Put zapata channel(s) into database
*
* @param integer $id
* @return integer $count = number of channels created
*/

function putZapataChannel($id) {

  global $db;

  $zchannel = 0; // Zapata channel number
  $count    = 0; // Number of channels created

  $ports=getZapataPort($id); // Fetch port information

  if(is_array($ports)) {
    foreach ($ports as $p) {
      $id    = $p["id"];
      $order = $p["order"];

      // Check if Port already has channels = continue;

      $channel=getZapataChannel($id);

      if(is_array($channel)) { // Port has channel(s) 
        continue;
      }

      // How many channels do we create for this type

      switch($p["type"]) {

        // SPAN CARDS
        case "E1":  $type=$p["type"]; $channels=31; break;
        case "T1":  $type=$p["type"]; $channels=24; break;

        // FXO,FXS
        case "FXO": $type=$p["type"]; $channels=1;  break;
        case "FXS": $type=$p["type"]; $channels=1;  break;
        case "FX?": $type=$p["type"]; $channels=1;  break;

        // Unknown - Abort
        default:    $type=$p["type"]; $channels=0;  break;
      }
 
      if ($channels == 0) { 
        error_log("Abort - putZapataChannel(): unknown type=[$type] id=[$id]");
        continue;
      }

      // Default device type(s) for zaptel.conf

      if($dchan) unset($dchan);

      switch($p["device"]) {

        // Supported Span devices
        case "PRI":  if($type=="E1") $dchan=16; if($type=="T1") $dchan=24;
                     $type="PRI"; $device="bchan";  $signalling=""; break;
        case "E&M":  $type="E&M"; $device="e&m";    $signalling=""; break;
        case "FX?":  $type="FX?"; $device="unused"; $signalling=""; break;

        // Supported FXO,FXS
        case "fxsks":  $device=$p["device"]; $signalling="fxs_ks";  break;
        case "fxsls":  $device=$p["device"]; $signalling="fxs_ls";  break;
        case "fxsgs":  $device=$p["device"]; $signalling="fxs_gs";  break;
        case "fxoks":  $device=$p["device"]; $signalling="fxo_ks";  break;
        case "fxols":  $device=$p["device"]; $signalling="fxo_ls";  break;
        case "fxogs":  $device=$p["device"]; $signalling="fxo_gs";  break;
        case "unused": $device=$p["device"]; $signalling="";        break;

        // Device unknown, e.g. span w/o valid device
        default:       $device="unused";     $signalling="";        break;
      } 

      for ($pchannel=1; $pchannel <= $channels; $pchannel++) {
        $qry="INSERT INTO `cw_zapata_channel` (`port_id`,`order`,`group`,`type`,`device`,`signalling`,`pchannel`,`zchannel`,`available`) VALUES ('$id','$order','NULL','$type','$device','$signalling','$pchannel','0','1')";
        $res=$db->Execute($qry);

        if($db->Affected_Rows())
          $count++;
      } // End insert into DB

      if($dchan) { // Set dchannel if the card has a dchannel
        $qry="UPDATE `cw_zapata_channel` SET `device` = 'dchan' where `port_id` = '$id' AND `pchannel` = '$dchan'";
        $res=$db->Execute($qry);
      }

    } // End for each port

    // Map local channels to zaptel channel numbers

    $channel  = getZapataChannel(0);
    $zchannel = 0;

    foreach($channel as $c) {
      $id     = $c["id"];
      $device = $c["device"];
      $zchannel++;    
      $qry="UPDATE `cw_zapata_channel` SET `zchannel` = '$zchannel' WHERE `id` = '$id'";
      $res=$db->Execute($qry);
    }
  } 
  return $count; 
}


/**
* Delete zapata channel(s) for port
*
* @param integer $id
* @return integer = no of deleted channels
*/

function delZapataChannel($id) {

  global $db;

  $res = true;
  $ret = 0;

  if ($id != "") $condition = "WHERE `port_id` = '$id'";

  $qry="DELETE FROM `cw_zapata_channel` $condition";
  $res=$db->Execute($qry);
  $ret=$db->Affected_Rows();

  return $ret;
}


/**
* Update zapata channel(s)
*
* @param ZapChannelArray
* @return integer $count = rows updated, 0 = no array or nothing updated
*/

function updateZapataChannel($channel) {

  global $db;
  $count=0;

  if(is_array($channel)) {
    foreach ($channel as $c) {
      $id         = $c["id"];
      $port_id    = $c["port_id"];
      $order      = $c["order"];
      $group      = $c["group"];
      $type       = $c["type"];
      $device     = $c["device"];
      $signalling = $c["signalling"];
      $pchannel   = $c["pchannel"];
      $zchannel   = $c["zchannel"];
      $available  = $c["available"];

      $qry="UPDATE `cw_zapata_channel` SET `order` = '$order', `group` = '$group',`type` = '$type', `device` = '$device', `signalling` = '$signalling', `pchannel` = '$pchannel', `zchannel` = '$zchannel', `available` = '$available' WHERE `id` = '$id'";
      $res=$db->Execute($qry);

      if($db->Affected_Rows())
        $count++;
    }
  }
  return $count;
}


/**
* Get zapata tonezone(s), ordered by name
*
* @param integer $id
* @return ZapToneZoneArray
*/

function getZapataToneZone($id) {

  global $db;

  if ($id != "") $condition = "WHERE `id` = '$id'";

  $qry="SELECT * FROM `cw_zapata_zone` ORDER BY `name` $condition";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) 
    $ret[]=$arr;

  return $ret;
}


/**
* Write configuration file zaptel.conf and driver load order zaptel
*
* @param boolean $restart=false
* @return integer = number of bytes written into config file, 0 = error
*/

function writeZaptelInitConfFile($restart=true) {

  global $db,$voiceoneWSDocumentRoot,$voTools;

  $zaptel=""; // zaptel.conf = zaptel config file
  $driver=""; // zaptel      = driver load order
  $pspans=""; // write the span config section
  $pchans=""; // write the chan config section
  $pother=""; // write the fxos config section
  $ptones=""; // write the tone config section
  $kmodule_last="";
  $kmodule_list="";
  $card_id_last="";

  // File update timestamp
  $update=date("r");

  // Sections of config file: zaptel.conf
  // ------------------------------------
  // 1 = header information
  // 2 = span config
  // 3 = span channel config
  // 4 = FXO,FXS cards
  // 5 = Tonezone

  // Sections of module file: zaptel
  // -------------------------------
  // 1 = header information 
  // 2 = TELEPHONEY=yes
  // 3 = # DEBUG=yes
  // 4 = MODULES="module1 module2"

  $zaptel.="##################################################\n";
  $zaptel.="# VoiceOne Configurator \n";
  $zaptel.="# file: zaptel.conf     \n";
  $zaptel.="# date: $update         \n";
  $zaptel.="##################################################\n";
  $zaptel.="#\n";

  $driver.="##################################################\n";
  $driver.="# VoiceOne Configurator \n";
  $driver.="# file: zaptel          \n";
  $driver.="# date: $update         \n";
  $driver.="##################################################\n";
  $driver.="#\n";
  $driver.="TELEPHONY=yes\n";
  $driver.="# DEBUG=yes\n\n";

  $cards=getZapataCard(0);
  $ports=getZapataPort(0);

  if(is_array($ports)) {
    foreach ($ports as $p) {
      $id      = $p["id"];
      $port    = $p["port"];
      $type    = $p["type"]; 
      $device  = $p["device"];
      $span    = $p["span"]; 
      $timing  = $p["timing"];
      $lbo     = $p["lbo"];
      $framing = $p["framing"];
      $coding  = $p["coding"];
      $crc4    = $p["crc4"];
      $yellow  = $p["yellow"];
      $card    = $p["card"];
      $card_id = $p["card_id"];

      if($crc4)   $crc4=",crc4";     else $crc4="";
      if($yellow) $yellow=",yellow"; else $yellow="";

      // Write current card name into zaptel.conf sections
      // Write kmodule card name into zaptel

      if ($card_id != $card_id_last) {

        // Get card kmodule name and add to driver list

        $kmodule = $cards[$card-1]["driver"];
        if (strcmp($kmodule,$kmodule_last) != 0) {
          if (strlen($kmodule)) {
            // Add driver to driver list
            $kmodule_list.=$kmodule;
            $kmodule_list.=" ";
          }
        }

        // Get card name for zaptel.conf section(s)

        $cardnm = $cards[$card-1]["name"];
        if ($span) {
          $pspans.="\n\n# $cardnm\n\n";
          $pchans.="\n\n# $cardnm\n";
        } else {
          $pother.="\n\n# $cardnm\n\n";
        }
      }

      if ($span) { // This is a span type card (multiple channels)

        // Check if we have a valid Span configuration

        if (!ctype_digit($timing) && !ctype_digit($lbo) ||
           ($framing != "ccs" && $framing != "cas" && 
            $framing != "esf" && $framing != "d4") ||
           ($coding != "hdb3" && $coding  != "ami" && $coding != "b8zs")) {
          $pspans.="# "; // Span config invalid = comment out 
        }
        $pspans.="span=$span,$timing,$lbo,$framing,$coding$crc4$yellow\n";

        $channel=getZapataChannel($id);

        // Do we also need to validate the channel config?
        // Guess it should be ok as we validate it already
        // when we setup the channel config ....

        if (!is_array($channel)) {
          // No channels found that should never happen
          $pchans.="\n# Port: $port - No channel config found.";
        } else {
          $devicename=""; // Start with no name
          $zchannel=0;
          foreach($channel as $c) {
            // Is this a new device for the channel list ?
            if (strcmp($devicename,$c["device"]) != 0) {
              // Is this the end of a device list ?
              if ($devicename != "") { // Skip first channel
                // Had the last list more than one channel ?
                if (($c["zchannel"]-$zchannel) == 1) {
                  $pchans.="-";
                  $pchans.=$zchannel; 
                }
              }
              $devicename = $c["device"];
              $pchans.="\n$devicename";
              $pchans.="=";
              $pchans.= $c["zchannel"];
            } else {
              // Still same device for this channel
              $zchannel = $c["zchannel"];
            }
          } // End foreach channel 

          // Catch the last device list
          if (($c["zchannel"]-$zchannel) == 0) {
            $pchans.="-";
            $pchans.=$zchannel; 
          }
        } // End span channels

      } else {  // FXO,FXS card(s) only have one channel per port
        $channel=getZapataChannel($id);
        if (!is_array($channel)) {
          // No channels found that should never happen
          $pother.="# Port: $port - No channel config found.\n";
        } else {
          $zchannel=$channel[0]["zchannel"];
          $device  =$channel[0]["device"];
          $pother.="$device=$zchannel\n";
        }
      }
     
      $kmodule_last = $kmodule;
      $card_id_last = $card_id;
    }
  } // End foreach loop


  // Add tone section for zaptel.conf

  $ptones.="\n\n# ZAPTEL (Tonezone) \n";

  $tones=getZapataToneZone(0);
  if(is_array($tones)) {
    foreach ($tones as $t) {
      $loadzone=$t["loadzone"];
      $defz=$t["defaultzone"];
      $name=$t["name"];
      if($loadzone=="1") {
        $loadzone_list.="\nloadzone=$name";
      }
      if($defz=="1") {
        $defaultzone=$name;
      }
    } // End foreach loop
  }

  if(strlen($defaultzone) == 0) {
    $defaultzone="us";
  }
  $ptones.=$loadzone_list."\n\n";
  $ptones.="defaultzone=".$defaultzone."\n\n";


  // Merge Sections for zaptel.conf

  $zaptel.=$pspans;
  $zaptel.=$pchans;
  $zaptel.=$pother;
  $zaptel.=$ptones;


  // Merge kmodule_list zaptel

  $driver.="MODULES=\"";
  if (strlen($kmodule_list)) {
    $driver.=$kmodule_list;
  } else {
    $driver.="ztdummy ";
  }  
  $driver = substr($driver,0,-1);
  $driver.= "\"\n\n";


  // Write zaptel.conf into sandbox

  $filename=$voiceoneWSDocumentRoot."/sandbox/zaptel.conf";
  $ret=file_put_contents($filename,$zaptel); 

  // Unable to write file sandbox
  if($ret==0) return($ret);

  // Write zaptel into sandbox

  $filename=$voiceoneWSDocumentRoot."/sandbox/zaptel";
  $ret=file_put_contents($filename,$driver); 

  // Unable to write file sandbox
  if($ret==0) return($ret);

  // Parameter restart is only useful during testing
  // to avoid crashing asterisk all the time.
  // I'm not aware of any cases were we should not 
  // restart everything after changing config files

  if ($restart!==false) {

    unset($out);

    error_log("sudo $voTools asterisk_stop");exec("sudo $voTools asterisk_stop",$out);errArr($out);  //stop service asterisk
    error_log("sudo $voTools zaptel_init stop");exec("sudo $voTools zaptel_init stop",$out);errArr($out);      //stop service zaptel (unload drivers)

    error_log("sudo ".$voTools);exec("sudo ".$voTools." zaptel_conf \"$voiceoneWSDocumentRoot/sandbox\" ",$out);errArr($out); // move the zaptel configuration files
    errArr($out);
    error_log("sudo $voTools zaptel_init start");exec("sudo $voTools zaptel_init restart",$out); errArr($out);   // start service zaptel
    sleep(5);
    //error_log("sudo $voTools asterisk_start");exec("sudo $voTools asterisk_start",$out); errArr($out); //start the asterisk    
    error_log("Completed!");
  }

  // If we can get more info from vo-tools we could return a more useful
  // return code, now its only the number of bytes written in the last
  // config file

  return $ret;
}


/**
* Get zapata group(s) by id (group_id)
*
* @param integer $id
* @return ZapGroupArray
*/

function getZapataGroup($id,$onlyCustomOption=false) {

  global $db;

  $count=0;

  if ($id != "") $condition = "WHERE `id` = '$id'";
  $qry="SELECT * FROM `cw_zapata_group` $condition";
  $res=$db->Execute($qry);

  while ($arr=$res->FetchRow()) { 
    unset($opt);
    $group_id   = $arr["id"];         // Group id
    $name       = $arr["name"];       // Name of the group
    $msns       = $arr["msns"];       // List of numbers 
    $signalling = $arr["signalling"]; // Signalling for this group

    $channel=getChannel(getChannelFromEntity($group_id));
    $arr["speeddial"]     = $channel[0]["speeddial"];
    $arr["channel_order"] = $channel[0]["order"];

    // extract the ports, channels of the group

    unset($port);

    $qry="SELECT * FROM `cw_zapata_portgroup` WHERE `group` = '$group_id' ORDER BY 'id'";
    $res2=$db->Execute($qry);

    while ($arr2=$res2->FetchRow()) {
      $port[] = $arr2["port"];
    }
    $arr["port"] = $port; // ID from port table

    // extract the phonenumber of the group

    $arr["msns"]=split("\|",$msns);

    // read the specific group option

    $arr["option"]=getEntityOption($id);
    
    $ret[]=$arr;

  }
  return $ret;
}


/**
* Put zapata group
*
* @param ZapGroup
* @return integer entity_id, 0 = error
*/

function putZapataGroup($group,$reload=true) {

  global $db;

  $writeZaptelInitFile = false;

  // del group if already exists

  if (isset($group["id"]) and $group["id"] != "") {
    $channel["id"]=getChannelFromEntity($group["id"]);
    delZapataGroup($group["id"],false);
    $entity["id"]=$group["id"];
  }
  $ret=1;


  // Get option array keys for signalling, channel, group

  $signallingOptionArr = getOptionByName("signalling","ZAP");
  $signallingOptionId  = $signallingOptionArr[0]["id"];
  $channelOptionArr    = getOptionByName("channel","ZAP");
  $channelOptionId     = $channelOptionArr[0]["id"];
  $groupOptionArr      = getOptionByName("group","ZAP");
  $groupOptionId       = $groupOptionArr[0]["id"];

  
  // Save old zapata group number, used in a few places in
  // voiceone so better not make this dynamic (e.g. rules)

  if (is_array($group["option"])) {
    foreach ($group["option"] as $o) {
      if ($o["key"] == "$groupOptionId")
        $group_no = $o["value"];
    }
  }

  // get a new group number for zapata 0-63 if we don't have one already

  if (!isset($group_no)) {

    $qry="SELECT DISTINCT `group` FROM `cw_zapata_group` ORDER BY `group`";
    $res=$db->Execute($qry);

    if ($res->RecordCount() > 0) {
      while ($arr=$res->FetchRow()) {
        $groups[]=$arr["group"];
      }
      for ($zapgroup = 0; $zapgroup < 64; $zapgroup++) {
        if (!in_array($zapgroup,$groups)) {
          $group_no = $zapgroup;
          break;
        }
      }
      // Zap groups are only valid from 0-63
      if ($zapgroup > 63)
        return 0;
    } else {
      // This must be the first group
      $group_no = 0;
    }
  }
  $name       = $group["name"];
  $msns       = msns2string($group["msns"]);
  $ports      = $group["port"];
  $type       = $group["type"];
  $did        = $group["did"];
  $signalling = $group["signalling"];
  $rule       = "0";


  // find matching device for signalling

  if ($type == "FXO" || $type == "FXS") {
    switch($signalling) {
      case "fxs_ks":  $device="fxsks";  break;
      case "fxs_ls":  $device="fxsls";  break;
      case "fxs_gs":  $device="fxsgs";  break;
      case "fxo_ks":  $device="fxoks";  break;
      case "fxo_ls":  $device="fxols";  break;
      case "fxo_gs":  $device="fxogs";  break;
      default: return 0;
    }
  }


  // check if we have a port array to group

  if (!is_array($ports)) 
    return 0;


  // create a new entity

  $entity["key"]="0";
  $entity["type"]=ZAPATA_GROUP;
  $entity_id=putEntity($entity);

  if (is_object($entity_id) or $entity_id == 0) 
    return 0;


  // extract the rule set ID

  $rule=extractRuleFromOption($group["option"],ZAP);


  // insert the zapata group

  $qry="INSERT INTO cw_zapata_group (`id`,`group`,`name`,`rule`,`msns`,`type`,`did`,`signalling`) VALUES ($entity_id,'$group_no','$name',$rule,'$msns','$type','$did','$signalling')";
  $res=$db->Execute($qry);

  if ($res===false) return new soap_fault('Server','','Zapata Group creation error, mysql INSERT command failed'); 


  // put the new Channel

  $channel['technology'] = ZAP;
  $channel['resource']   ="g".$group_no;
  $channel['key']        = $entity_id;
  $channel['msns']       = $group["msns"];
  $channel['rule']       = $rule;
  $channel['trunk']      = 1;
  $channel['did']        = $did;
  $channel['speeddial']  = $group["speeddial"];

  $channelId = putChannel($channel);  


  // Remove signalling, channel, group from option array

  // Added because the getZapataGroup, putZapataGroup
  // combination used when adding, removing cards, ports
  // returned options in reverse order as well as some 
  // items were duplicated if the value varied from the
  // old value e.g. channel = 1,2,3 channel 1,2 resulted
  // in two entires channel ... 

  if (is_array($group["option"])) {
    foreach ($group["option"] as $o) {
      if ($o["key"] != "$signallingOptionId" &&
          $o["key"] != "$channelOptionId"    &&
          $o["key"] != "$groupOptionId")
      $option[]=$o;
    }
    foreach ($option as $oid => $row) {
      $key[$oid]   = $row["key"];
      $value[$oid] = $row["value"];
    }
    array_multisort($key, SORT_NUMERIC, SORT_ASC, $option);
    $group["option"]=$option;
  }


  // Add zap group_no to group option array

  $group["option"][]=array("key"=>"$groupOptionId", "value"=>"$group_no");


  // Add signalling to group option array

  $group["option"][]=array("key"=>"$signallingOptionId", "value"=>$signalling);


  // insert the ports, channels into cw_zapata_portgroup

  // First, expand PRI and E&M ports

  if ($type == "PRI" || $type == "E&M") {
    foreach($ports as $p) {
      $qry="SELECT `port_id` FROM `cw_zapata_channel` WHERE `id` = '$p' ORDER BY `order`, `port_id`, `pchannel`";
      $res=$db->Execute($qry);
      while ($arr=$res->FetchRow()) {
      // Note: This fixes the port_group dup's when
      //       installing, deleting cards
      // $port_id[]=$arr["port_id"];
        $port_id_list[]=$arr["port_id"];
      }
      $port_id=array_unique($port_id_list);
    }

    // Now get all channels of device type dchan or e&m
    // This should filter out the "unused" and "dchan"
    // We may need this info later to implements adding 
    // and deleting channels for fractional E1,T1 
   
    foreach ($port_id as $p) {
      $qry="UPDATE `cw_zapata_channel` SET `group` = '$name', `signalling` = '$signalling', `available` = '0' WHERE `port_id` = '$p' AND ( `device` = 'bchan' OR `device` = 'e&m')";
      $db->Execute($qry);

      $qry="SELECT `id` FROM `cw_zapata_channel` WHERE `port_id` = '$p' AND ( `device` = 'bchan' OR `device` = 'e&m' ) ORDER BY `order`, `port_id`, `pchannel`";
      $res=$db->Execute($qry);
      while ($arr=$res->FetchRow())  {
        $port[]=$arr["id"];
      }
    }
    $ports = $port; // New expanded portlist

  } else { // FXO, FXS part

    foreach ($ports as $p) {
      $qry="UPDATE `cw_zapata_channel` SET `group` = '$name', `available` = '0' WHERE `id` = '$p'";
      $db->Execute($qry);

      $qry="UPDATE `cw_zapata_channel` SET `signalling` = '$signalling', `device` = '$device' WHERE `id` = '$p'";
      $db->Execute($qry);

      if($db->Affected_Rows()) {
        $writeZaptelInitFile = true;   // Update zaptel.conf as well
      }
    }
  }


  // Write the ports into portgroup table

  foreach($ports as $p) { // Port = id from cw_zapata_channel table
    $qry = "INSERT INTO cw_zapata_portgroup (`port`,`group`) VALUES ($p,$entity_id)";
    $res=$db->Execute($qry);
  }


  // read all the installed port  

  $arrPort=getZapataChannel(0);

  // Problem: cw_option_entity as well as putEntityOption are not well
  //          equipped to handle zapata, so we can only use a key once
  //          per entity. This breaks grouping of FXO, FXS ports if
  //          they use different devices hmmm ....

  // Build simple Channel List until cw_entity_option is sorted out

  $portList="";
  $zchannel="";
  $schannel="";
  
  if (@is_array($arrPort))
  foreach ($arrPort as $p) {
    if (@in_array($p["id"],$ports)) { // Found a Channel List Member
      // Start of portList with the 1st channel
      if ($portList == "") {
         $portList = $p["zchannel"];
         $zchannel = $p["zchannel"];
         $schannel = $zchannel;
         continue;
      }

      // Are channels in sequence?
      if (($p["zchannel"]-$zchannel) == 1) {
        $zchannel = $p["zchannel"];
        continue;
      } else { 
        if ($zchannel != $schannel) {
          $portList.="-";
          $portList.=$zchannel; 
        }
        $portList.= ",";
        $portList.= $p["zchannel"];
        $zchannel = $p["zchannel"];
        $schannel = $zchannel;
      }
    }
  }
  if ($zchannel != $schannel) {
    $portList.="-";
    $portList.=$zchannel; 
  }


  // Add channel list to group option array

  $group["option"][]=array("key"=>"$channelOptionId", "value"=>$portList);

  putEntityOption($group["option"],ZAP,$entity_id,$group);


  // update incoming context in ast_config for this trunk group

  $context = "trunk_".$channelId;

  $qry="UPDATE `ast_config` SET `var_val` = '$context' WHERE `filename` = 'zapata.conf' AND `category` = 'channels' AND `var_name` = 'context' AND `zsection` = '$name'";
  $db->Execute($qry);


  // write the global Variables

  generateGlobalVar();


  // Do we need to write a new zaptel.conf file

  if ($writeZaptelInitFile == true) {
    if(!writeZaptelInitConfFile(true)) {
      $ret = 0; // Well if this fails, ...
    }
  }

  // reload chan_zap
  //
  // Note: Some parameter changes may not work according
  //       to unverified Information from the Internet.

  if ($reload!==false) {

    sendCommand("unload chan_zap.so");
    //error_log($ret);
    sleep(2);

    sendCommand("load chan_zap.so");
    //error_log($ret);
    sleep(2);

    sendCommand("reload");
    //error_log($ret);
    sleep(3);
  }
  
  if ($ret>0) $ret=$entity_id;

  return $ret;
}


/**
* Delete zapata group and return channels into pool
*
* @param integer id 
* @return integer 1 = ok, 0 = error
*/

function delZapataGroup($id,$reload=true) {

  global $db;
  
  // retrieve the queue name and set channels available

  $group = getZapataGroup($id);
  $name  = $group[0]["name"];

  $qry="UPDATE `cw_zapata_channel` SET `group`='', `available`='1' WHERE `group`='$name'";
  $db->Execute($qry);

  // remove the options written in the asterisk table

  delEntityOption($id, ZAP);
  
  $qry="DELETE FROM cw_zapata_group WHERE id='$id'";
  $res=$db->Execute($qry);
  if ($res===false) return new soap_fault('Server','','Zapata group remove error, mysql DELETE command failed');

  $qry="DELETE FROM cw_zapata_portgroup WHERE `group`='$id'";
  $res=$db->Execute($qry);
  if ($res===false) return new soap_fault('Server','','Zapata port group remove error, mysql DELETE command failed');

  // update the Channels

  $channelId=getChannelFromEntity($id);
  if ($channelId!=0) delChannel($channelId);

  $ret=delEntity($id);

  if ($reload!==false) {

    //reload the chan_misdn
    $ret=sendCommand("unload chan_zap.so");
    //error_log($ret);
    sleep(2);
  
    $ret=sendCommand("load chan_zap.so");
    //error_log($ret);
    sleep(2);
  
    $ret=sendCommand("reload");
    //error_log($ret);
    sleep(3);
  }
  return 1;
}


// ****************************************************************************
// ---- NEW ZAPATA PART ENDS HERE ---------------------------------------------
// ****************************************************************************

//  error_log(print_r($ret,true));

?>